package erfgame.core.entity.factory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


import erfgame.core.DescribedValue;
import erfgame.core.DescribedValueFactory;
import erfgame.core.Dimensions3D;
import erfgame.core.Range;
import erfgame.core.Sequence;
import erfgame.core.entity.CompositeAssociation;
import erfgame.core.entity.CompositeAssociationType;
import erfgame.core.entity.Entity;
import erfgame.core.entity.EntityByTypeFactory;
import erfgame.core.entity.EntityType;
import erfgame.core.entity.Monster;
import erfgame.core.entity.Part;
import erfgame.core.world.World;
import erfgame.core.world.terrain.MaterialType;

/**
 * Creates a typical tree (branches etc)
 * @author chris
 *
 */
public class GenericTreeByTypeFactory implements EntityByTypeFactory {

	public static final String KEY_GIRTH = "girth";
	public static final String KEY_LENGTH = "length";
	public static final String KEY_ANGLE = "angle";
	public static final String KEY_DENSITY = "density";
	
	public static final int DEFAULT_MAX_LEVEL = 10;
	
	private Sequence<Double> random;
	
	private Range.Double trunkGirthRange;	
	private Range.Double branchGirthRange;
	private DescribedValueFactory<Double, Double> girthDescriptionFactory;
	private double maxTerminationGirth;
	
	private Range.Double trunkLengthRange;
	private Range.Double branchLengthRange;
	private DescribedValueFactory<Double, Double> lengthDescriptionFactory;
	
	private Range.Double trunkAngleRange;
	
	private Range.Integer branchNumberRange;	
	private Range.Double branchAngleRange;
	private DescribedValueFactory<Double, Double> angleDescriptionFactory;
	private MaterialType branchMaterial;
	private EntityType branchType;
	private EntityType trunkType;
	private CompositeAssociationType branchAssociationType;
	
	private Range.Double foliageRange;
	private MaterialType foliageMaterial;
	private DescribedValueFactory<Double, Double> foliageDescriptionFactory;
	private EntityType foliageType;
	private CompositeAssociationType foliageAssociationType;  
	
	private int maxLevel;
	
	private Dimensions3D dimensions;
	
	public GenericTreeByTypeFactory() {
		this( DEFAULT_MAX_LEVEL );
	}
	
	public GenericTreeByTypeFactory( int maxLevel ) {
		this.maxLevel = maxLevel;
	}
	
	public Dimensions3D getDimensions() {
		return dimensions;
	}

	public void setDimensions(Dimensions3D dimensions) {
		this.dimensions = dimensions;
	}

	public Sequence<Double> getRandom() {
		return random;
	}

	public void setRandom(Sequence<Double> random) {
		this.random = random;
	}

	public Range.Double getBranchGirthRange() {
		return branchGirthRange;
	}

	public void setBranchGirthRange(Range.Double girthRange) {
		this.branchGirthRange = girthRange;
	}

	public double getMaxTerminationGirth() {
		return maxTerminationGirth;
	}

	public void setMaxTerminationGirth(double maxTerminationGirth) {
		this.maxTerminationGirth = maxTerminationGirth;
	}

	public Range.Double getBranchLengthRange() {
		return branchLengthRange;
	}

	public void setBranchLengthRange(Range.Double lengthRange) {
		this.branchLengthRange = lengthRange;
	}

	public Range.Double getBranchAngleRange() {
		return branchAngleRange;
	}
	
	public void setBranchAngleRange(Range.Double branchAngleRange) {
		this.branchAngleRange = branchAngleRange;
	}

	public Range.Double getTrunkGirthRange() {
		return trunkGirthRange;
	}

	public void setTrunkGirthRange(Range.Double trunkGirthRange) {
		this.trunkGirthRange = trunkGirthRange;
	}

	public Range.Double getTrunkLengthRange() {
		return trunkLengthRange;
	}

	public void setTrunkLengthRange(Range.Double trunkLengthRange) {
		this.trunkLengthRange = trunkLengthRange;
	}

	public Range.Double getTrunkAngleRange() {
		return trunkAngleRange;
	}

	public void setTrunkAngleRange(Range.Double trunkAngleRange) {
		this.trunkAngleRange = trunkAngleRange;
	}

	public MaterialType getBranchMaterial() {
		return branchMaterial;
	}
	
	public void setBranchMaterial(MaterialType branchMaterial) {
		this.branchMaterial = branchMaterial;
	}

	public Range.Double getFoliageRange() {
		return foliageRange;
	}

	public void setFoliageRange(Range.Double foliageRange) {
		this.foliageRange = foliageRange;
	}

	public MaterialType getFoliageMaterial() {
		return foliageMaterial;
	}

	public void setFoliageMaterial(MaterialType foliageMaterial) {
		this.foliageMaterial = foliageMaterial;
	}

	public Range.Integer getBranchNumberRange() {
		return branchNumberRange;
	}

	public void setBranchNumberRange(Range.Integer branchNumberRange) {
		this.branchNumberRange = branchNumberRange;
	}
	
	public DescribedValueFactory<Double, Double> getGirthDescriptionFactory() {
		return girthDescriptionFactory;
	}

	public void setGirthDescriptionFactory(
			DescribedValueFactory<Double, Double> girthDescriptionFactory) {
		this.girthDescriptionFactory = girthDescriptionFactory;
	}

	public DescribedValueFactory<Double, Double> getLengthDescriptionFactory() {
		return lengthDescriptionFactory;
	}

	public void setLengthDescriptionFactory(
			DescribedValueFactory<Double, Double> lengthDescriptionFactory) {
		this.lengthDescriptionFactory = lengthDescriptionFactory;
	}

	public DescribedValueFactory<Double, Double> getAngleDescriptionFactory() {
		return angleDescriptionFactory;
	}

	public void setAngleDescriptionFactory(
			DescribedValueFactory<Double, Double> angleDescriptionFactory) {
		this.angleDescriptionFactory = angleDescriptionFactory;
	}

	public EntityType getBranchType() {
		return branchType;
	}

	public void setBranchType(EntityType branchType) {
		this.branchType = branchType;
	}

	public EntityType getTrunkType() {
		return trunkType;
	}

	public void setTrunkType(EntityType trunkType) {
		this.trunkType = trunkType;
	}

	public CompositeAssociationType getBranchAssociationType() {
		return branchAssociationType;
	}

	public void setBranchAssociationType(
			CompositeAssociationType branchAssociationType) {
		this.branchAssociationType = branchAssociationType;
	}

	public DescribedValueFactory<Double, Double> getFoliageDescriptionFactory() {
		return foliageDescriptionFactory;
	}

	public void setFoliageDescriptionFactory(
			DescribedValueFactory<Double, Double> foliageDescriptionFactory) {
		this.foliageDescriptionFactory = foliageDescriptionFactory;
	}
	
	

	public EntityType getFoliageType() {
		return foliageType;
	}

	public void setFoliageType(EntityType foliageType) {
		this.foliageType = foliageType;
	}

	public CompositeAssociationType getFoliageAssociationType() {
		return foliageAssociationType;
	}

	public void setFoliageAssociationType(
			CompositeAssociationType foliageAssociationType) {
		this.foliageAssociationType = foliageAssociationType;
	}

	public int getMaxLevel() {
		return maxLevel;
	}

	public void setMaxLevel(int maxLevel) {
		this.maxLevel = maxLevel;
	}

	public Entity createEntity(World world, EntityType type) {
		Part body = createBody( world );
		Monster monster = new Monster(
				world.getNextEntityId(), 
				type, 
				body
		);
		monster.setDimensions(dimensions);
		return monster;
	}

	public Part createBody( World world ) {
		double girthMultiplier = random.next();
		double girth = branchGirthRange.getValue(girthMultiplier);
		double lengthMultiplier = random.next();
		double length = branchLengthRange.getValue(lengthMultiplier);

		double angleMultiplier = random.next();
		double angle = trunkAngleRange.getValue(angleMultiplier);
		
		List<CompositeAssociation> branches = createBranches( world, girthMultiplier, angle, 0 );
		Part trunk = new Part(
				world.getNextEntityId(), 
				trunkType, 
				branchMaterial, 
				branches
		);
		Map<String, DescribedValue<Object>> descriptions = getBranchDescriptions( 
				girthMultiplier, 
				girth, 
				lengthMultiplier, 
				length, 
				angleMultiplier, 
				angle
		);
		trunk.setDescriptions(descriptions);
		
		return trunk;
	}
	
	protected List<CompositeAssociation> createBranches( World world, double parentGirthMultiplier, double parentAngle, int level ) {
		int numBranches = this.branchNumberRange.getValue(random.next());
		ArrayList<CompositeAssociation> result = new ArrayList<CompositeAssociation>( numBranches );
		if( level < maxLevel ) {
			for( int i=numBranches; i>0; ) {
				i--;
				// TODO : make sure the branches are splayed out
				double angleMultiplier = random.next();
				double angle = this.branchAngleRange.getValue(angleMultiplier);
				Part part = createBranch( world, parentGirthMultiplier, angle, angleMultiplier, level+1 );
				if( part != null ) {
					CompositeAssociation association = new CompositeAssociation(this.branchAssociationType, part);
					result.add( association );
				}
			}
		}
		return result;
	}
	
	protected Part createBranch( World world, double parentGirthMultiplier, double angle, double angleMultiplier, int level ) {
		double girthMultiplier = parentGirthMultiplier * random.next();
		double girth = branchGirthRange.getValue(girthMultiplier);
		if( girth > maxTerminationGirth ) {
			double lengthMultiplier = random.next() * girthMultiplier;
			double length = branchLengthRange.getValue(lengthMultiplier);

			List<CompositeAssociation> branches = createBranches( world, girthMultiplier, angle, level );
			if( branches.size() == 0 ) {
				Part foliage = createFoliage( world );
				branches.add(
						new CompositeAssociation(
								foliageAssociationType, 
								foliage
						)
				);
			}			
			
			Part result = new Part(
					world.getNextEntityId(), 
					branchType, 
					branchMaterial, 
					branches
			);
			Map<String, DescribedValue<Object>> descriptions = getBranchDescriptions(
					girthMultiplier, 
					girth, 
					lengthMultiplier, 
					length, 
					angleMultiplier, 
					angle
			);
			result.setDescriptions(descriptions);
			return result;
		} else {
			return null;
		}
	}
	
	@SuppressWarnings("unchecked")
	private Part createFoliage( World world ) {
		Part foliage = new Part(
				world.getNextEntityId(), 
				foliageType, 
				foliageMaterial
		);
		// add in foliage to description
		double densityMultiplier = random.next();
		double density = foliageRange.getValue(densityMultiplier);
		DescribedValue foliageDescription = foliageDescriptionFactory.describe(densityMultiplier, density);
		HashMap<String, DescribedValue<Object>> descriptions = new HashMap<String, DescribedValue<Object>>(1);
		descriptions.put(KEY_DENSITY, foliageDescription);
		foliage.setDescriptions(descriptions);
		return foliage;
	}
	
	@SuppressWarnings("unchecked")
	private Map<String, DescribedValue<Object>> getBranchDescriptions( 
			double girthMultiplier,
			double girth,
			double lengthMultiplier,
			double length, 
			double angleMultiplier, 
			double angle
	) {
		HashMap<String, DescribedValue<Object>> result = new HashMap<String, DescribedValue<Object>>(3);
		
		DescribedValue girthDescription = this.girthDescriptionFactory.describe(
				girthMultiplier,
				girth
		);
		result.put( KEY_GIRTH, girthDescription );
		
		DescribedValue lengthDescription = this.lengthDescriptionFactory.describe(
				lengthMultiplier, 
				length
		);
		result.put( KEY_LENGTH, lengthDescription );
		
		DescribedValue angleDescription = this.angleDescriptionFactory.describe(
				angleMultiplier, 
				angle
		);
		result.put( KEY_ANGLE, angleDescription );
		
		return result;
	}
}
